Cloud9の環境を作成する権限を持ったIAMユーザーを作ってみた
Cloud9の環境を作成する権限をハンズオン参加者に付与したい
こんにちは、のんピ(@non____97)です。
皆さんはCloud9の環境を作成する権限をハンズオン参加者に付与したいと思ったことはありますか? 私はあります。
2時間程度の少人数のハンズオンだと1つのAWSアカウントに相乗りする形でCloud9の環境を用意したいことがあります。
この時、AWSのAPIを叩くことはなく、Cloud9のターミナル上で操作が完結するのであれば、基本的にCloud9だけを触れる権限のみを付与したいです。余計な権限は付与させたくはありません。
ということで、なるべく付与する権限が少なくなるように設定してみました。
やってみた
AWS CDKのコード
以下記事をベースにAWS CDKでIAMユーザーおよびIAMグループ、IAMポリシーを作成します。
付与している権限は以下のとおりです。
- 自IAMユーザーのタグの変更
- MFA設定や自IAMユーザーのパスワード変更
- Cloud9の環境の作成
- 特定のインスタンスタイプのみ許可
- 環境名は
<自IAMユーザー名>*
のみ許可
- Cloud9の環境作成にあたって必要なDescribe、List、UpdateUserSettings
- サービスリンクドロール
AWSCloud9SSMAccessRole
の作成、関連付け - インスタンスプロファイル
AWSCloud9SSMInstanceProfile
の作成、関連付け - SSMセッションマネージャーの接続
Cloud9環境の削除や更新の権限は付与していません。以下ドキュメントに記載のとおりDeleteEnvironment
やUpdateEnvironment
などはRBACに対応しておらず、リソースもarn:${Partition}:cloud9:${Region}:${Account}:environment:${ResourceId}
と環境名が使用できないためです。
今回のシチュエーションである2時間程度の少人数のハンズオンであれば、こういった操作は主催者側で対応できるかなと感じ、参加者には付与しませんでした。
IAMポリシーの設計をするにあたって以下AWS公式ドキュメントが非常に参考になりました。
IAMグループ、IAMポリシーを作成している箇所のコードは以下のとおりです。
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
export interface GroupConstructProps {}
export class GroupConstruct extends Construct {
readonly group: cdk.aws_iam.IGroup;
constructor(scope: Construct, id: string, props?: GroupConstructProps) {
super(scope, id);
const tagPolicy = new cdk.aws_iam.ManagedPolicy(this, "TagPolicy", {
statements: [
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
resources: [
"arn:aws:iam::" +
cdk.Stack.of(this).account +
":user/${aws:username}",
],
actions: ["iam:ListUserTags", "iam:UntagUser", "iam:TagUser"],
}),
],
});
const selfManagedMfaPolicy = new cdk.aws_iam.ManagedPolicy(
this,
"SelfManagedMfaPolicy",
{
statements: [
new cdk.aws_iam.PolicyStatement({
sid: "SelfManagedMfa",
effect: cdk.aws_iam.Effect.ALLOW,
resources: [
"arn:aws:iam::" +
cdk.Stack.of(this).account +
":mfa/${aws:username}*",
"arn:aws:iam::" +
cdk.Stack.of(this).account +
":user/${aws:username}*",
],
actions: [
"iam:ChangePassword",
"iam:CreateVirtualMFADevice",
"iam:DeleteVirtualMFADevice",
"iam:DeactivateMFADevice",
"iam:EnableMFADevice",
"iam:GetLoginProfile",
"iam:GetUser",
"iam:GetUserPolicy",
"iam:ListGroupsForUser",
"iam:ListAttachedUserPolicies",
"iam:ListUserPolicies",
"iam:ResyncMFADevice",
"iam:ListMFADevices",
],
}),
],
}
);
const cloud9Policy = new cdk.aws_iam.ManagedPolicy(this, "Cloud9Policy", {
statements: [
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
resources: ["*"],
actions: ["cloud9:CreateEnvironmentEC2"],
conditions: {
StringLike: {
"cloud9:EnvironmentName": "${aws:username}*",
"cloud9:InstanceType": [
"t3.nano",
"t3.micro",
"t3.small",
"t3.medium",
"t3.large",
],
},
Null: {
"cloud9:OwnerArn": "true",
},
},
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
resources: ["*"],
actions: [
"cloud9:DescribeEnvironments",
"cloud9:DescribeEnvironmentStatus",
"cloud9:DescribeEnvironmentMemberships",
"cloud9:ListEnvironments",
"cloud9:ListTagsForResource",
"cloud9:UpdateUserSettings",
"ec2:DescribeVpcs",
"ec2:DescribeSubnets",
"ec2:DescribeInstanceTypeOfferings",
"ec2:DescribeRouteTables",
],
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
resources: ["arn:aws:iam::*:role/service-role/*"],
actions: ["iam:ListRoles", "iam:ListInstanceProfilesForRole"],
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
resources: [
"arn:aws:iam::*:role/service-role/AWSCloud9SSMAccessRole",
],
actions: ["iam:ListInstanceProfilesForRole", "iam:CreateRole"],
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
resources: [
"arn:aws:iam::*:role/service-role/AWSCloud9SSMAccessRole",
],
actions: ["iam:AttachRolePolicy"],
conditions: {
StringEquals: {
"iam:PolicyARN":
"arn:aws:iam::aws:policy/AWSCloud9SSMInstanceProfile",
},
},
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
resources: [
"arn:aws:iam::*:role/service-role/AWSCloud9SSMAccessRole",
],
actions: ["iam:PassRole"],
conditions: {
StringEquals: {
"iam:PassedToService": "ec2.amazonaws.com",
},
},
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
resources: [
"arn:aws:iam::*:instance-profile/cloud9/AWSCloud9SSMInstanceProfile",
],
actions: [
"iam:CreateInstanceProfile",
"iam:AddRoleToInstanceProfile",
],
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
resources: ["*"],
actions: ["iam:CreateServiceLinkedRole"],
conditions: {
StringEquals: {
"iam:AWSServiceName": "cloud9.amazonaws.com",
},
},
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
resources: ["arn:aws:ec2:*:*:instance/*"],
actions: ["ssm:StartSession", "ssm:GetConnectionStatus"],
conditions: {
StringLike: {
"ssm:resourceTag/aws:cloud9:environment": "*",
},
StringEquals: {
"aws:CalledViaFirst": "cloud9.amazonaws.com",
},
},
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
resources: ["arn:aws:ssm:*:*:document/*"],
actions: ["ssm:StartSession"],
}),
],
});
const group = new cdk.aws_iam.Group(this, "Default", {
managedPolicies: [tagPolicy, selfManagedMfaPolicy, cloud9Policy],
});
this.group = group;
}
}
全体は以下GitHubリポジトリに保存しています。
Cloud9の環境の作成
実際にCloud9の環境を作成してみます。
IAMユーザー名はnon-97-test-user1
です。
名前がnon-97-test-user1
でインスタンスタイプがt3.microの環境を作成します。その他はデフォルト値です。
1分ほどで作成が完了しました。
Cloud9で開く
で接続します。問題なく接続できていますね。
ちなみに環境名の先頭が自IAMユーザー名でなかったり、インスタンスタイプがt2.micro
など許可しているものでない場合はUser: arn:aws:iam::<AWSアカウント>:user/non-97-test-user1 is not authorized to perform: cloud9:CreateEnvironmentEC2 on resource: * because no identity-based policy allows the cloud9:CreateEnvironmentEC2 action
というようにエラーになります。
ハンズオンに参加している他のユーザーが作ったCloud9には接続できません。test2
という環境を他プリンシパルで作成しましたが、アクセスなし
となっています。
また、Cloud9の環境を10個作成してみました。問題なく動作しています。
クォータを確認すると、ユーザーごとに100環境作成できるようです。
Cloud9の裏側のEC2インスタンスも元気です。
ある程度の規模のハンズオンでも捌けそうですね。
1つのAWSアカウント上で複数のハンズオン参加者にCloud9環境を作成してもらう際に
Cloud9の環境を作成する権限を持ったIAMユーザーを作ってみました。
1つのAWSアカウント上で複数のハンズオン参加者にCloud9環境を作成してもらう際に参考にしてみてください。
Cloud9のターミナル上からAWSのAPIを叩く際は、IAMユーザーに権限を付与して、AWS Managed Temporary Credentialsを活用する形でも良いと思います。AWS Managed Temporary Credentialsの詳細は以下記事をご覧ください。
この記事が誰かの助けになれば幸いです。
以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!